package com.sysware.p2m.patch;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class SVNLogger {
    private PatchPublishContext context;

    private Repository svnRepository;
    private Git git;

    static Map<DiffEntry.ChangeType, String> FILE_OPERATE_TYPE = new HashMap<>();

    static {
        FILE_OPERATE_TYPE.put(DiffEntry.ChangeType.ADD, PatchPublishContext.SCHEMA_ADDED);
        FILE_OPERATE_TYPE.put(DiffEntry.ChangeType.MODIFY, PatchPublishContext.SCHEMA_MODIFIED);
        FILE_OPERATE_TYPE.put(DiffEntry.ChangeType.RENAME, PatchPublishContext.SCHEMA_MODIFIED);
        FILE_OPERATE_TYPE.put(DiffEntry.ChangeType.DELETE, PatchPublishContext.SCHEMA_DELETED);
    }

    private Logger logger = LoggerFactory.getLogger(getClass());

    public SVNLogger(PatchPublishContext context) {
        this.context = context;
        initLocalRepository();
    }

    /**
     * 基于git初始化、更新源码
     * @return
     */
    public void initLocalRepository(){
        File projectDir = new File(context.getProjectDir());
        if (!projectDir.exists()) {
            logger.error("git repository 【" + projectDir.getAbsolutePath() + "】 不存在 , 开始初始化 ......");
            projectDir.mkdirs();
            cloneGitRepositoryToLocal(Constants.MASTER, context.getSvnUrl(), context.getSvnUserName(), context.getSvnPwd(), projectDir);
            logger.info("git repository 【" + projectDir.getAbsolutePath() + "】 初始化完成");
        }
        try {
            git = Git.open(projectDir);
            svnRepository = git.getRepository();
        } catch (Exception e) {
            //e.printStackTrace();
        }

        String maxRevision = context.getMaxVersion();
        if ("head".equals(maxRevision)) {
            context.setMaxVersion(updateToHEAD());
        } else {
            checkout(maxRevision);
        }
    }

    /**
     * 1. 基于master分支更新到最新版本
     */
    private String updateToHEAD() {
        try {
            // 切换到master分支
            if (!svnRepository.getBranch().equals(Constants.MASTER)) {
                git.checkout().setName(Constants.MASTER).call();
            }
            logger.info("切换【master】分支!");

            // 获取最新版本代码
            ProgressMonitor monitor = new TextProgressMonitor(new PrintWriter(System.out));
            PullResult pullResult = git.pull().setProgressMonitor(monitor).call();
            logger.info("【master】分支代码更新完成 " + pullResult.isSuccessful());
            printBranchInfo();
        } catch (Exception e) {
            //e.printStackTrace();
        }
        return getHeadId();
    }

    /**
     * 检出指定版本代码
     *
     * @param maxRevision
     */
    private void checkout(String maxRevision) {
        printBranchInfo();

        String branchName = "Branch_" + maxRevision;
        boolean branchExists = branchExists(branchName);
        /**
         * 1. 指定版本代码分支存在,就切换分支为工作分支
         */
        if (branchExists) {
            logger.info("【" + branchName + "】分支已存在!");
            try {
                if (!svnRepository.getBranch().equals(branchName)) {
                    git.checkout().setName(branchName).call();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            logger.info("设置【" + branchName + "】分支为当前工作分支!");
            return;
        }
        /**
         * 2. 不存在该分支操作步骤
         */
        // 2.1 切换到master分支
        try {
            if (!Constants.MASTER.equals(svnRepository.getBranch())) {
                git.checkout().setName(Constants.MASTER).call();
                logger.info("切换【master】分支!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 2.2 获取最新版本代码
        try {
            ProgressMonitor monitor = new TextProgressMonitor(new PrintWriter(System.out));
            PullResult pullResult = git.pull().setProgressMonitor(monitor).call();
            logger.info("【master】分支代码更新完成 " + pullResult.isSuccessful());
        } catch (GitAPIException e) {
            //e.printStackTrace();
        }
        printBranchInfo();

        //当前master版本就是最新的话，就不用生成新版本了
        if(maxRevision.equals(getHeadId())){
            return;
        }

        // 2.3 获取到指定版本代码(并将该分支设置为工作分支)
        try {
            CheckoutCommand checkoutCommand = git.checkout().setName(branchName);
            checkoutCommand.setCreateBranch(true).setStartPoint(maxRevision);
            checkoutCommand.call();
            logger.info("设置【" + branchName + "】分支为当前工作分支!");
        } catch (GitAPIException e) {
            //e.printStackTrace();
        }
        printBranchInfo();
    }

    /**
     * 该分支是否存在
     *
     * @param branchName
     * @return
     */
    private boolean branchExists(String branchName) {
        List<Ref> refList = null;
        try {
            refList = git.branchList().call();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
        boolean branchExists = false;
        if (refList != null) {
            for (Ref ref : refList) {
                if (ref.getName().equals("refs/heads/" + branchName)) {
                    branchExists = true;
                    break;
                }
            }
        }
        return branchExists;
    }

    private void printBranchInfo() {
        try {
            logger.info("current branch:" + svnRepository.getBranch() + "\t head revision:" + getHeadId());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载或更新代码指定版本代码到本地
     *
     * @param url        svn或git url地址
     * @param username   用户名
     * @param password   密码
     * @param projectDir 本地项目路径
     * @return
     * @throws Exception
     */
    private void cloneGitRepositoryToLocal(String branch, String url, String username, String password, File projectDir) {
        try {
            CloneCommand cloneCommand = Git.cloneRepository().setURI(url).setDirectory(projectDir);
            if (StringUtils.isEmpty(branch)) {
                cloneCommand.setCloneAllBranches(true);
            } else {
                cloneCommand.setBranch(branch);
            }
            if (url.toLowerCase().startsWith("http") && StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
                UsernamePasswordCredentialsProvider user = new UsernamePasswordCredentialsProvider(username, password);
                cloneCommand.setCredentialsProvider(user);
            }
            //打印进度
            cloneCommand.setProgressMonitor(new TextProgressMonitor(new PrintWriter(System.out)));

            cloneCommand.call();
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }

    /**
     * @param minRevision 先提交的版本号
     * @param maxRevision 后提交的版本号(为空则获取最新版本)
     * @return
     * @throws Exception
     * @describe : 获取从minRevision到maxRevision的svn记录
     * @date : 2015年2月4日下午1:32:12
     * @author :
     */
    public List<RevCommit> logList(Repository svnRepository, String minRevision, String maxRevision) throws Exception {
        if (StringUtils.isEmpty(maxRevision)) {
            maxRevision = getHeadId();
        }
        List<RevCommit> revCommitList = new ArrayList<RevCommit>();
        RevWalk revWalk = new RevWalk(svnRepository);
        RevCommit startRevCommit = revWalk.parseCommit(svnRepository.resolve(maxRevision));
        RevCommit endRevCommit = revWalk.parseCommit(svnRepository.resolve(minRevision));
        //发现提交时间顺序有误时，自动调换开始和结束版本号
        if (startRevCommit.getCommitTime() < endRevCommit.getCommitTime()) {
            RevCommit temp = startRevCommit;
            startRevCommit = endRevCommit;
            endRevCommit = temp;
        }
        revWalk.markStart(startRevCommit);
        for (RevCommit revCommit : revWalk) {
            revCommitList.add(revCommit);
            if (revCommit.equals(endRevCommit)) {
                logger.info("Found from 【" + endRevCommit.getId().getName() + "】 to 【" + startRevCommit.getId().getName() + "】, stopping walk");
                break;
            }
        }
        revWalk.dispose();

        //按时间正序排列版本记录
        Collections.reverse(revCommitList);

        return revCommitList;
    }

    /**
     * 获取代码库的最新版本号
     *
     * @return HEAD REVISION ID
     */
    public String getHeadId() {
        try {
            // 本地仓库最新版本号
            ObjectId objectId = svnRepository.resolve("HEAD");
            if (objectId != null) {
                return objectId.getName();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成svnlog日志文件
     *
     * @date : 2015年11月9日下午4:01:30
     * @author : P2M
     */
    public boolean generateSVNLog() {
        try {
            return generateSVNLog(context.getMinVersion(), context.getMaxVersion(), context.svnLogFilePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 获取指定版本区间代码的提交日志记录(按日志生成时间从前往后的顺序)
     *
     * @param startVersion 开始版本号
     * @param endVersion   结束版本号
     * @param logFile      生成日志文件的本地路径
     * @return
     * @throws Exception
     */
    private boolean generateSVNLog(String startVersion, String endVersion, String logFile) throws Exception {
        // 初始化log file
        File svnLogDir = new File(context.getPublishDir());
        if (!svnLogDir.exists()) {
            svnLogDir.mkdirs();
        }
        File svnLog = new File(logFile);
        if (svnLog.exists() && svnLog.isFile()) {
            svnLog.delete();
        }
        svnLog.createNewFile();

        boolean flag = true;
        List<RevCommit> revCommitList = logList(svnRepository, startVersion, endVersion);

        if (revCommitList != null && revCommitList.size() > 1) {
            BufferedWriter out = null;
            try {

                //指定文件为utf-8，以防止在不同系统生成此文件时依赖系统默认编码，从而导致为utf-8或者GBK；
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(svnLog), "utf-8"));

                for (int i = 1; i < revCommitList.size(); i++) {
                    RevCommit oldLogEntry = revCommitList.get(i - 1);
                    RevCommit logEntry = revCommitList.get(i);
                    out.newLine();
                    out.write(PatchPublishContext.SCHEMA_REVISION + logEntry.getName());
                    out.newLine();
                    out.write("Author: " + logEntry.getAuthorIdent().getName());
                    out.newLine();
                    out.write("Date: " + simpleDateFormat.format(logEntry.getAuthorIdent().getWhen()));
                    out.newLine();
                    out.write("Message: ");
                    out.newLine();
                    out.write(logEntry.getShortMessage());
                    out.newLine();
                    out.write("----");

                    List<DiffEntry> diffEntryList = diff(oldLogEntry.toObjectId().getName(), logEntry.toObjectId().getName());
                    if (diffEntryList != null) {
                        for (DiffEntry diffEntry : diffEntryList) {
                            out.newLine();
                            out.write(FILE_OPERATE_TYPE.get(diffEntry.getChangeType()) + " ");
                            if(DiffEntry.ChangeType.DELETE.equals(diffEntry.getChangeType())){
                                out.write(diffEntry.getOldPath());
                            }else{
                                out.write(diffEntry.getNewPath());
                            }
                        }
                    }
                    out.newLine();
                }
            } catch (Exception e) {
                flag = false;
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        logger.info("generateSVNLog 【" + logFile + "】 " + flag);
        return flag;
    }

    public List<DiffEntry> diff(String oldObjectId, String newObjectId) throws Exception {
        AbstractTreeIterator oldTree = prepareTreeParser(svnRepository, oldObjectId);
        AbstractTreeIterator newTree = prepareTreeParser(svnRepository, newObjectId);
        List<DiffEntry> diffList = git.diff().setNewTree(newTree).setOldTree(oldTree)
                .setShowNameAndStatusOnly(true).call();
        return diffList;
    }

    private AbstractTreeIterator prepareTreeParser(Repository repository, String objectId) throws IOException {
        // from the commit we can build the tree which allows us to construct the TreeParser
        RevWalk walk = new RevWalk(repository);
        RevCommit commit = walk.parseCommit(repository.resolve(objectId));
        CanonicalTreeParser treeParser = new CanonicalTreeParser();
        treeParser.reset(repository.newObjectReader(), walk.parseTree(commit.getTree().getId()).getId());
        walk.dispose();
        return treeParser;
    }
}
